﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using IWMAG.Scripting.AST.ObjectModel;
using IWMAG.Scripting.Types;
using IWMAG.Scripting.AST.TerminalExpressions;
using IWMAG.Scripting.Helpers;
using IWMAG.Scripting.Compiler;
using IWMAG.Scripting.Interpreter;

namespace IWMAG.Scripting.AST.Operators {
    internal class MemberOperator : CodeExpression {
        public CodeExpression Object;
        public CodeExpression Member;
        public bool IsMethod = false;
        public bool IsField = false;
        public bool IsProperty = false;
        public bool IsFakeMethod = false;
        internal Type ClrType;
        internal string MemberName;
        protected CodeType Type;

        public MemberOperator(Compiler.Lexeme l, CodeExpression obj, CodeExpression mem) : base(l) {
            Object = obj;
            Member = mem;
        }

        public override void Verify(Helpers.CompileTable sym, List<Compiler.IUserInfo> error) {
            Object.Verify(sym, error);

            try {
                Member.Verify(sym, error);
            } catch (VerifyException e) {
                if (!error.LastOrDefault().ToString().StartsWith("Variable")) {
                    throw e;
                } else {
                    error.RemoveAt(error.Count - 1);
                }
            }

            if (!(Member is VariableExpression)) {
                SendError(error, "Member dereference was parsed incorrectly. This should never happen.");
            }

            VariableExpression v = Member as VariableExpression;
            MemberName = v.Name;

            if (Object.TypeOf() is ArrayType) {
                if (v.Name != "length") {
                    SendError(error, "Invalid member of array");
                } else {
                    Type = CodeType.Int;
                    IsFakeMethod = true;
                }
                return;
            } else if (Object.TypeOf().Equals(CodeType.String)) {
                if (v.Name != "length") {
                    SendError(error, "Invalid member of string");
                } else {
                    Type = CodeType.Int;
                    IsFakeMethod = true;
                }
                return;
            }


            ReferenceType r = Object.TypeOf() as ReferenceType;
            if (r == null || !sym.Imports.Contains(r)) {
                SendError(error, "Member dereference of non-CLR type " + Object.TypeOf().ToString() + ". Are you missing an import?");
            } else {
                Type t = sym.Imports[(Object.TypeOf() as ReferenceType)];
                ClrType = t;
                try {
                    Type = Marshaller.Marshal(t.GetProperty(MemberName).GetGetMethod().ReturnType, sym);
                    IsProperty = true;
                } catch {
                    try {
                        Type = Marshaller.Marshal(t.GetField(MemberName).FieldType, sym);
                        IsField = true;
                    } catch {
                        SendError(true, error, "Member " + MemberName + " does not exist");
                    }
                }
            }
        }

        public override CodeType TypeOf() {
            return Type;
        }

        public override IEnumerable<Continuation> ValueOf(Interpreter.ScriptEngine engine, RuntimeTable sym, Interpreter.Reference<Interpreter.RunValue> val) {
            Reference<RunValue> obj = RunValue.NoValue;
            foreach (Continuation wait in Object.ValueOf(engine, sym, obj)) yield return wait;

            if (IsFakeMethod) {
                if (MemberName == "length") {
                    val.ReferencedValue.Value = obj.ReferencedValue.ArrayValue.Count;
                }
            } else if (IsMethod || (!IsMethod && !IsField && !IsProperty)) {
                val.ReferencedValue = obj.ReferencedValue;
            } else if (IsField) {
                engine.IsExterned = true;
                val.ReferencedValue.Value = ClrType.GetField(MemberName).GetValue(obj.ReferencedValue.Value);
                engine.IsExterned = false;
            } else if (IsProperty) {
                engine.IsExterned = true;
                val.ReferencedValue.Value = ClrType.GetProperty(MemberName).GetValue(obj.ReferencedValue.Value, null);
                engine.IsExterned = false;
            }

            yield break;
        }
    }
}
